package czopyk.model.validation;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.espertech.esper.collection.Pair;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import czopyk.model.cost.CostMapper;
import czopyk.model.request.CompositeRequest;
import czopyk.model.request.LoadClass;
import czopyk.model.request.Request;
import czopyk.model.structure.Cache;
import czopyk.model.structure.Center;
import czopyk.model.structure.LoadBalancer;
import czopyk.model.structure.LoadSelector;

public class Validator {

	private CostMapper costMapper;
	private LoadSelector loadSelector;
	
	private final Log log = LogFactory.getLog(getClass());
	
	//wszystkie typy danych żądań elementarnych z obciążenia
	private Set<LoadClass> getAllElementaryLoadClasses() {
		
		Queue<LoadClass> queue = new LinkedList<LoadClass>();
		
		queue.addAll(loadSelector.getLoad());
		
		Set<LoadClass> loadClasses = new HashSet<LoadClass>();

		while(!queue.isEmpty()) {
			LoadClass lc = queue.remove();
			
			if (lc.isComposite()) {
				for (LoadClass innerLc : lc.getComponents().keySet()) {
					if (!loadClasses.contains(innerLc.getType())) {
						queue.add(innerLc);
					}
				}
			}
			
			loadClasses.add(lc);
		}

		Iterator<LoadClass> iter = loadClasses.iterator();
		while(iter.hasNext()) {
			if (iter.next().isComposite()) {
				iter.remove();
			}
		}

		return loadClasses;
	}
	
	/**
	 * Walidacja poprawnosci calego systemu
	 * */
	public void validate(Center gate) {
		
		validateNoCycles(gate);

		// dla każdego typu walidacja na gate
		for (LoadClass lc : getAllElementaryLoadClasses()) {
			log.debug("Validating " + lc + "...");
			if (!validate(lc, gate, costMapper)) {
				throw new RuntimeException("Model not consistent: load class '"
						+ lc + "' not fulfillable.");
			}
		}
	}

	protected boolean validate(LoadClass lc, Center c, CostMapper cm) {
		log.debug("validating " + lc + " in center '" + c.getName() + "'...");
		if (!(c instanceof Cache)
				&& cm.isCenterCapableOfServingLoadClass(c.getCenterClass(), lc)) {
			// spelnia
			return true;
		} else if (!c.getAllNext().isEmpty()) {
			// moze cos dalej spelnia
			if (!(c instanceof LoadBalancer)) {
				for (Center ce : c.getAllNext()) {
					if (validate(lc, ce, cm)) {
						// któraś pogałąź spełnia
						return true;
					}
				}
				log.debug("Validation failed: no branch of center '" + c.getName() + "' fulfills " + lc);
				return false;
			} else {
				for (Center ce : c.getAllNext()) {
					if (!validate(lc, ce, cm)) {
						// jeśli któraś pogałąź nie spełnia
						log.debug("Validation failed: branch '" + ce.getName() + "' of '" + c.getName() + "' does not fulfill " + lc);
						return false;
					}
				}
				return true;
			}
		}
		// wpp
		log.debug("Validation failed: center '" + c.getName() + "' is not a cache and is an ending node");
		return false;
	}

	/**
	 * Walidacja poprawnosci calego systemu
	 * 
	 * czy nie ma cykli
	 * */
	public void validateNoCycles(Center gate) {
		BiMap<Center, Integer> s1 = HashBiMap.create();
		List<Center> l1 = new LinkedList<Center>();
		l1.add(gate);
		int i = 0;
		while (!l1.isEmpty()) {
			Center c = l1.remove(0);
			if (!s1.containsKey(c)) {
				s1.put(c, i++);
				for (Center c1 : c.getAllNext()) {
					l1.add(c1);
				}
			}
		}

		int s = s1.size();
		boolean[][] paths = new boolean[s][s];
		for (boolean[] row : paths) {
			Arrays.fill(row, false);
		}

		List<Pair<Center, Center>> l = new LinkedList<Pair<Center, Center>>();
		for (Center target : gate.getAllNext()) {
			l.add(new Pair<Center, Center>(gate, target));
		}
		Pair<Center, Center> path;
		while (!l.isEmpty()) {
			path = l.remove(0);
			// jesli jeszcze nie ma polaczenia
			if (!paths[s1.get(path.getFirst())][s1.get(path.getSecond())]) {
				paths[s1.get(path.getFirst())][s1.get(path.getSecond())] = true;
				for (Center target : path.getSecond().getAllNext()) {
					// przedlurzenie sciezki
					l.add(new Pair<Center, Center>(path.getFirst(), target));
					// kolejne polaczenie bezposrednie
					l.add(new Pair<Center, Center>(path.getSecond(), target));
				}
			}
		}

		for (i = 0; i < s; ++i) {
			for (int j = 0; j < s; ++j) {
				if (paths[i][j] && paths[j][i]) {
					String c1 = s1.inverse().get(i).getName();
					throw new RuntimeException("Cycle detected including center '" + c1
							+ "'.");
				}
			}
		}
	}
	
	/**
	 * Czy w strukturze requestu są jakieś cykle?
	 * @param cr
	 */
	public void validate(CompositeRequest cr) {
		final LoadClass lc = cr.getLc();
		for (Request e : cr.getComponents()) {
			validate(e, lc);
		}
	}

	public void validate(Request er, final LoadClass lc) {
		if (lc.equals(er.getLc())) {
			throw new IllegalStateException("Cyclic agregation with load class: " + lc);
		}
		if (er instanceof CompositeRequest) {
			for (Request e : ((CompositeRequest) er).getComponents()) {
				// dzieci tej samej klasy
				validate(e, lc);
				// inne requesty
				validate((CompositeRequest)e);
			}
		}
	}

	public void setCostMapper(CostMapper costMapper) {
		this.costMapper = costMapper;
	}

	public void setLoadSelector(LoadSelector loadSelector) {
		this.loadSelector = loadSelector;
	}

}
